iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Security

《Nyahello!從零開始的 30 天 malware 學習日誌》系列 第 29 篇

【Day 29】Anti-Debugging:惡意程式如何發現自己正在被 Debug?

  • 分享至 

  • xImage
  •  

前言

前面幾章開始使用 Debugger 動態分析 Malware,但 Malware 作者當然也知道分析人員會使用 Debugger。

因此 Malware 可能加入 Anti-Debugging 技術,用來:

  • 判斷自己是否正在 Debugger 中執行
  • 發現 Debugger 後改變執行流程
  • 故意 Crash 或結束程式
  • 干擾 Breakpoint、Single-step 等功能

Anti-Debugging 的方法非常多,本章主要介紹實務上常見的幾類技巧,以及分析時如何辨認與繞過。

【聲明】由於時間問題本日文章由 AI 生成QQ

Windows Debugger Detection

最直接的 Anti-Debugging 方法就是:

檢查目前 Process 是否有 Debugger Attached。

Malware 可以透過 Windows API、PEB 等記憶體結構,甚至系統中留下的 Debugger 痕跡進行判斷。

Using the Windows API

IsDebuggerPresent

最基本的 API:

IsDebuggerPresent();

它會檢查 Process Environment Block(PEB)中的 Debugger 狀態。

概念很簡單:

0       → 沒有 Debugger
nonzero → 有 Debugger

因此如果在 Malware Imports 或 Assembly 中看到 IsDebuggerPresent,就應該立刻懷疑 Anti-Debugging。

CheckRemoteDebuggerPresent

功能與 IsDebuggerPresent 類似。

雖然名稱有 Remote,但它不是檢查遠端電腦,而是可以檢查本機指定 Process 是否被 Debug。

NtQueryInformationProcess

這是 ntdll.dll 中的 Native API。

Malware 可以指定:

ProcessDebugPort = 0x7

查詢 Process 是否正在被 Debug。

如果沒有 Debugger:

DebugPort = 0

否則會得到 Debug Port。

OutputDebugString

OutputDebugString 原本是把字串送給 Debugger 顯示,但也能反過來偵測 Debugger。

書中的做法大致是:

SetLastError()
↓
OutputDebugString()
↓
GetLastError()
↓
比較 Error Value

根據 Error Value 是否改變,判斷 Debugger 是否存在。

這類 API-based Anti-Debugging 最簡單的繞過方法通常是:

修改 API 呼叫後的 Flag、Return Value 或 Conditional Jump,強迫 Malware 走「沒有 Debugger」的路徑。


Manually Checking Structures

直接呼叫 IsDebuggerPresent 太明顯,因此 Malware 也可以:

自己去讀 Windows 的資料結構。

其中最重要的就是:

PEB(Process Environment Block)

在 32-bit Windows Process 中,PEB 可以透過:

fs:[30h]

取得。

BeingDebugged Flag

PEB 裡面有一個:

BeingDebugged

位於 PEB Offset:

+0x02

因此可能看到:

mov eax, fs:[30h]
mov ebx, [eax+2]
test ebx, ebx

概念就是:

fs:[30h]
   ↓
  PEB
   ↓
BeingDebugged
   ↓
是否有 Debugger

繞過方式很直接:

  1. 把 BeingDebugged 改成 0

  2. 修改 Flag

  3. 強迫 Conditional Jump 走 No Debugger 路線

所以看到:

fs:[30h]

時要特別注意,它是很典型的 Anti-Debugging 特徵。


ProcessHeap Flags

Debugger 啟動 Process 時,Heap 的設定也可能不同。

PEB Offset:

0x18 → ProcessHeap

Heap Header 中又包含:

Flags
ForceFlags

Malware 可以檢查這些 Flag,判斷 Process 是否由 Debugger 啟動。

例如:

mov eax, fs:[30h]
mov eax, [eax+18h]
cmp [eax+10h], 0
jne DebuggerDetected

要注意這些 Offset 會隨 Windows 版本而不同,因此不是看到某個固定 Offset 就一定代表 Anti-Debugging。


NTGlobalFlag

PEB 中還有另一個常被檢查的欄位:

NTGlobalFlag

教材中的 32-bit 範例位於:

PEB + 0x68

當 Process 從 Debugger 啟動時,某些 Heap Debugging Flags 會被開啟,常見組合值為:

0x70

因此 Malware 可能:

mov eax, fs:[30h]
cmp [eax+68h], 70h
jz DebuggerDetected

繞過方式同樣可以修改 Flag,或直接修改後面的 Branch。


Checking for System Residue

Malware 不一定要直接檢查 Process,也可以尋找分析環境留下的痕跡。

例如檢查 Registry:

HKLM\SOFTWARE\Microsoft\
Windows NT\CurrentVersion\AeDebug

也可能搜尋:

Debugger executable
Debugger process
Debugger window

例如:

FindWindow("OLLYDBG", 0);

如果找到名稱為 OLLYDBG 的 Window,就知道 OllyDbg 正在執行。

因此 Malware Analysis 環境本身也可能成為被偵測的依據。


Identifying Debugger Behavior

前面的技巧是在找「Debugger 是否存在」。

另一種思路則是:

檢查程式有沒有出現 Debugging 才會產生的行為。

主要包含:

INT Scanning
Code Checksum
Timing Checks

INT Scanning

Software Breakpoint 通常是 Debugger 把原本的 Instruction 暫時替換成:

INT 3
Opcode = 0xCC

因此 Malware 可以掃描自己的 Code:

搜尋 0xCC
↓
找到
↓
可能有人設 Software Breakpoint

教材範例就是取得自己的 Code Address 後掃描 0xCC。

一種繞過方式是:

改用 Hardware Breakpoint。

因為 Hardware Breakpoint 不需要把原本的 Code 改成 0xCC。


Code Checksums

Malware 也可以計算自己 Code 的:

CRC
MD5

如果分析人員設置 Software Breakpoint 或 Patch Code,原始 Bytes 改變,Checksum 就會不同。

概念:

Calculate checksum
        ↓
Compare expected checksum
        ↓
不同 → Code 被修改

分析時如果看到程式大量遍歷自己的 Code,最後又拿結果與固定值比較,就要考慮是不是 Anti-Debugging Checksum。


Timing Checks

Timing Check 是非常常見的 Anti-Debugging。

原因很簡單:

正常執行:

Instruction → Instruction → Instruction

Debug 時:

Instruction
↓
Breakpoint
↓
分析人員看半天
↓
Single-step

執行時間自然差很多。

Malware 因此可以:

取得 Time A
↓
執行一些 Code
↓
取得 Time B
↓
B - A
↓
太久 → Debugger

RDTSC

最典型的 Timing Check 是:

rdtsc

它會取得 CPU Time Stamp Counter,結果放入:

EDX:EAX

Malware 執行兩次 rdtsc:

RDTSC
↓
執行 Code
↓
RDTSC
↓
計算差值

如果差距太大,就推測正在被 Debug。

QueryPerformanceCounter / GetTickCount

Windows API 也可以做到類似效果:

QueryPerformanceCounter
GetTickCount

例如:

a = GetTickCount();

MaliciousActivityFunction();

b = GetTickCount();

if ((b-a) > threshold)
    // Debugger Detected

繞過 Timing Check 最簡單的方法之一,是不要在兩次計時之間慢慢 Single-step。

可以直接 Run 過 Timing Check,在後面設 Breakpoint;或直接修改比較結果與 Conditional Jump。


Interfering with Debugger Functionality

前面是在「發現 Debugger」,接下來則是:

直接讓 Debugger 不好用。

本章介紹:

TLS Callbacks
Exceptions
Interrupts
Debugger Vulnerabilities

TLS Callbacks

一般使用 Debugger 時,很容易認為:

Entry Point = 第一個執行的 Instruction

但這不一定正確。

Windows 支援:

TLS Callback

TLS Callback 可以在程式的正常 Entry Point 之前執行。

因此 Malware 可以:

Program Load
↓
TLS Callback
↓
Anti-Debugging / Malicious Code
↓
Entry Point

如果分析人員只在 Entry Point 開始 Debug,就可能已經漏掉一段重要 Code。

PE 中可能看到:

.tls

在 IDA Pro 中則可以使用:

CTRL-E

查看 Entry Points,其中也包含 TLS Callback。教材第 10 頁的 Figure 16-1、16-2 就分別展示了 PEview 中的 TLS Table,以及 IDA 的 TLS Callback Entry Point。


Using Exceptions

Exception 也可以拿來判斷 Debugger。

正常情況:

Exception
↓
Program Exception Handler

但 Debugger 存在時通常會先攔截 Exception:

Exception
↓
Debugger
↓
Program

因此 Malware 可以故意製造 Exception,再觀察自己的 Handler 是否正常收到。

分析 Malware 時,如果 Debugger 一直停在各種 Exception,不一定代表程式壞掉,也可能是 Anti-Debugging。

教材建議分析時讓 Debugger 將 Exception 傳回程式處理。


Inserting Interrupts

INT 3

因為:

INT 3 = 0xCC

本來就是 Software Breakpoint 使用的 Interrupt,Malware 可以自己插入 INT 3,故意讓 Debugger 停下。

更特殊的是:

CD 03

也可以產生 INT 3。

教材指出,這種 2-byte INT 3 在特定舊版 Debugger 中可能讓 EIP 處理錯誤,使 Debugger 中的執行流程與正常執行不同。

INT 2D

INT 2D 與 Kernel Debugger 有關,也可以利用 Debugger 對 Interrupt 的處理差異進行 Anti-Debugging。

ICE / ICEBP

另一個比較特殊的是:

ICEBP
Opcode = F1

它會產生 Single-step Exception。

如果程式正在被 Single-step,Debugger 可能把它當成一般 Single-step Exception,導致 Malware 原本設定的 Exception Handler 沒有執行。

因此教材給出的重點很直接:

遇到 icebp 時,不要 Single-step 過去。


Debugger Vulnerabilities

最後一類方法不是「偵測 Debugger」,而是:

直接利用 Debugger 本身的 Bug。

本章以舊版 OllyDbg 為主要例子。

PE Header Vulnerabilities

例如 PE Optional Header 中:

NumberOfRvaAndSizes

正常 DataDirectory 數量是:

0x10

Windows Loader 對超過這個範圍的值有自己的處理方式,但舊版 OllyDbg 的處理不同。

因此 Malware 可以故意設:

NumberOfRvaAndSizes = 0x99

讓 Malware 正常執行,但 OllyDbg 載入時出錯。

教材第 14 頁的 Figure 16-5 就是在說明這個差異。

另一個例子是:

SizeOfRawData

Malware 可以故意設定極大的值,例如:

77777777h

使舊版 OllyDbg Crash。

這種情況可以手動修正 PE Header,或改用不受影響的 Debugger。


OutputDebugString Vulnerability

舊版 OllyDbg 還存在 OutputDebugString 相關問題。

Malware 可以傳入特殊 Format String,例如大量:

%s%s%s%s...

使 Debugger Crash。

所以看到非常異常的 OutputDebugString 呼叫,也可能不是普通 Debug Message,而是 Anti-Debugging。


結語

Chapter 16 的 Anti-Debugging 可以整理成四個核心方向:

類型 主要概念
Debugger Detection API、PEB、BeingDebugged、ProcessHeap、NTGlobalFlag
Debugger Behavior INT Scanning、Checksum、Timing Check
Debugger Interference TLS Callback、Exception、INT 3、INT 2D、ICEBP
Debugger Vulnerability 利用 PE Header 或 Debugger 本身的 Bug

實際分析 Malware 時,如果程式莫名其妙在 Conditional Jump 後結束、Debugger 中和正常執行結果不同,或頻繁看到 fs:[30h]、Debugger Detection API、RDTSC、GetTickCount、Exception 等行為,都應該考慮 Anti-Debugging。

而繞過方法其實有一個共同核心:

先找出 Malware 用什麼資訊判斷 Debugger,再讓這個判斷得到「沒有 Debugger」的結果。

可能是修改 Flag、Return Value、PEB、Conditional Jump,避開 Timing Check,改用 Hardware Breakpoint,或調整 Debugger 對 Exception 的處理。

Anti-Debugging 技巧很多,不太可能全部背下來。更重要的是建立辨識能力:當程式在 Debugger 裡出現不合理行為時,先懷疑 Malware 是否正在觀察或干擾 Debugger,而不是直接認為分析失敗。 這也是本章最後強調的重點。

參考資料

  • Practical Malware Analysis (Chapter 16)
  • chatGPT

本日貓味

https://ithelp.ithome.com.tw/upload/images/20260924/20183878S7zWNrdL2P.jpg


上一篇
【Day 28】Anti-Disassembly:反反組譯技術
下一篇
【Day 30】Packers and Unpacking:惡意程式的加殼與脫殼
系列文
《Nyahello!從零開始的 30 天 malware 學習日誌》 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言